#include "xlcomm/net/detail/acceptor.h"

#include <fcntl.h>
#include <unistd.h>

#include "xlcomm/base/logger.h"

namespace xlcomm {
namespace net {

int CreateNonblockSocket() {
  int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
                        IPPROTO_TCP);
  if (sockfd < 0) {
    LOG_SYSERROR(socket);
  }
  return sockfd;
}

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listen_addr,
                   bool reuse_port)
    : loop_(loop),
      accept_socket_(CreateNonblockSocket()),
      accept_channel_(loop, accept_socket_.sockfd()),
      listening_(false),
      idle_fd_(::open("/dev/null", O_RDONLY | O_CLOEXEC)) {
  XLCOMM_ASSERT(accept_socket_.sockfd() >= 0);
  accept_socket_.SetReuseAddr(true);
  accept_socket_.SetReusePort(reuse_port);
  accept_socket_.Bind(listen_addr);
  LOG_INFO << "Acceptor bind in " << listen_addr.ToIpPort()
           << ", acceptor_fd=" << accept_socket_.sockfd();
  accept_channel_.set_read_callback(std::bind(&Acceptor::HandleRead, this));
}

Acceptor::~Acceptor() {
  accept_channel_.DisableAll();
  accept_channel_.Remove();
  ::close(idle_fd_);
}

void Acceptor::Listen() {
  XLCOMM_ASSERT(loop_->IsInLoopThread());
  listening_ = true;
  accept_channel_.EnableReading();
  accept_socket_.Listen();
  LOG_INFO << "Acceptor(acceptor_fd:" << accept_socket_.sockfd()
           << ") is listening...";
}

void Acceptor::HandleRead() {
  XLCOMM_ASSERT(loop_->IsInLoopThread());
  InetAddress peer_addr;
  // FIXME: loop until no more (edge trigger必须loop)
  int conn_sock = accept_socket_.Accept(&peer_addr);
  if (conn_sock >= 0) {
    LOG_DEBUG << "Acceptor(acceptor_fd:" << accept_socket_.sockfd()
              << ") accepts one " << peer_addr.ToIpPort();
    if (new_connection_callback_) {
      // FIXME:
      // 传递sockfd不够理想，可以先创建Socket对象，然后std::move给回调，确保资源安全释放
      new_connection_callback_(conn_sock, peer_addr);
    } else {
      if (::close(conn_sock) < 0) {
        LOG_SYSERROR(close) << "conn_sock:" << conn_sock;
      }
    }
  } else {
    LOG_ERROR << "Acceptor(acceptor_fd:" << accept_socket_.sockfd()
              << ") HandleRead error!";
    // Read the section named "The special problem of
    // accept()ing when you can't" in libev's doc.
    // By Marc Lehmann, author of libev.
    if (errno == EMFILE) {
      ::close(idle_fd_);
      idle_fd_ = ::accept(accept_socket_.sockfd(), NULL, NULL);
      ::close(idle_fd_);
      idle_fd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }
}

}  // namespace net
}  // namespace xlcomm